<!--
-File         $Id:ExtendingXinc.html 123 2006-09-14 20:19:08Z mrook $
-License      GNU FDL (http://www.gnu.org/copyleft/fdl.html)
-Copyright    2002, turing
-Author       alex black, enigma@turingstudio.com
-->
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/2000/REC-xhtml1-20000126/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
  <head>
    <title>Xinc Guide - Extending Xinc</title>
    <meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1" />
    <link rel="Stylesheet" rev="Stylesheet" href="../css/Documentation.css" type="text/css" media="All" charset="iso-8859-1" />
</head>
<body>

<h1><a name="ExtendingXinc"></a>Extending Xinc</h1>

<p>Xinc was designed to be flexible and easily extensible. Xinc's existing core
  and optional tasks do provide a great deal of flexibility in processing files,
  performing database actions, and even getting user feedback during a build process.
  In some cases, however, the existing tasks just won't suffice and because of
  Xinc's open, modular architecture adding exactly the functionality you need
  is often quite trivial.</p>
<p>In this chapter we'll look primarily at how to create your own tasks, since
  that is probably the most useful way to extend Xinc. We'll also give some more
  information about Xinc's design and inner workings.</p>
<h2><a name="ExtensionPossibilities"></a>Extension Possibilities</h2>

<p>There are three main areas where Xinc can be extended: <a href="#Tasks">tasks</a>,
  <a href="#Types">types</a>, <a href="#Mappers">mappers</a>. The following sections
  discuss these options.</p>

<h3><a name="Tasks"></a>Tasks</h3>

<p> Tasks are pieces of codes that perform an atomic action like installing a
  file. Therefore a special worker class hast to be created and stored in a specific
  location, that actually implements the job. The worker is just the interface
  to Xinc that must fulfill some requirements discussed later in this chapter,
  however it can - but not necessarily must - use other classes, workers and libraries
  that aid performing the operations needed. </p>

<h3><a name="Types"></a>Types</h3>

<p> Extending types is a rare need; nevertheless, you can do it. A possible type
  you might implement is <em>urlset</em>, for example.</p>
<p>You may end up needing a new type for a task you write; for example, if you
  were writing the XSLTTask you might discover that you needed a special type
  for XSLTParams (even though in that case you could probably use the generic
  name/value Parameter type). In cases where the type is really only for a single
  task, you may want to just define the type class in the same file as the Task
  class, rather than creating an official stand-alone <em>Type</em>.</p>

<h3><a name="Mappers"></a>Mappers</h3>

<p> Creating new mappers is also a rare need, since most everything can be handled
  by the <a href="appendixes/appendixc-coretypes.html#regexpmapper">RegexpMapper</a>.
  The Mapper framework does provide a simple way for defining your own mappers
  to use instead, however, and mappers implement a very simple interface.</p>

<h2><a name="SourceLayout"></a>Source Layout</h2>

<h3><a name="FilesandDirectories"></a>Files And Directories</h3>

<p> Before you are going to start to extend Xinc let's have a look at the source
  layout. You should be comfortable with the organization of files witchin the
  source tree of Xinc before start coding. After you extracted the source distribution
  or checked it out from CVS you should see the following directory structure:</p>

<pre title="DistributionFileLayout">$Xinc_HOME
  |-- bin
  |-- classes
  |    `-- Xinc
  |         |-- filters
  |         |    `-- util
  |         |-- mappers
  |         |-- parser
  |         |-- tasks
  |         |    |-- ext
  |         |    |-- system
  |         |    |    `-- condition
  |         |    `-- user
  |         `-- types
  |-- docs
  |    `-- Xinc_guide
  `-- test
       |-- classes
	   `-- etc
</pre>
<p>
The following table briefly describes the contents of the major
directories:
</p>

<a name="tables.XincSourceTreeDirectories"></a>
<table>
  <caption>Xinc source tree directories</caption>
  <thead>
  <tr>
    <th>Directory</th>
    <th>Contents</th>
  </tr>
  </thead>
  <tbody>
  <tr>
    <td><p>bin</p></td>
    <td>
    <p>The basic applications (Xinc, configure) as well as the wrapper scripts
          for different platforms (currently Unix and Windows).</p>
    </td>
  </tr>
  <tr>
    <td><p>classes</p></td>
    <td><p>Repository of all the classes used by Xinc. This is the base directory
          that should be on the PHP include_path. In this directory you will find
          the subdirectory Xinc/ with all the Xinc relevant classes.</p></td>
  </tr>
  <tr>
    <td><p>docs</p></td>
    <td>
    <p>Documentation files. Generated books, online manuals as well as
       the PHPDoc generated API documentation.</p>
    </td>
  </tr>
  <tr>
    <td><p>test</p></td>
    <td>
    <p>A set of testcases for different tasks, mappers and types. If
       you are developing in CVS you should add a testcase for each
       implementation you check in.</p>
    </td>
  </tr>
  </tbody>
</table>

<p>Currently there is no distinction between the <em>source</em> layout and the
  <em>build</em> layout of Xinc. The <a href="#figures.MainDirLayout">figure
  above</a> shows the CVS tree that carries some additional files like the Xinc
  website. Later on there may be a buildfile to create a clean distribution tree
  of Xinc itself. </p>

<h3><a name="FileNamingConventions"></a>File Naming Conventions</h3>

<p> There are some filenaming conventions used by Xinc. Here's a quick rundown
  on the most basic conventions. A more detailed list can be found in [See Naming
  And Coding Standards]: </p>

<ul>
  <li>Filenames consist of no more or less than two elements: <em>name</em> and
    <em>extension</em> .</li>
  <li>Choose short descriptive filenames (must be less than 31 chars)</li>
  <li>Names must not contain dots.</li>
  <li>Files containing PHP code must end with the extension <em>.php</em> .</li>
  <li>There must be only one class per file (no procedural methods allowed, use
    a separate file for them), with the exception of &quot;inner&quot;-type /
    helper classes that can be declared in the same file as the &quot;outer&quot;
    / main class.</li>
  <li>The name portion of the file must be named exactly like the class it contains.</li>
  <li>Buildfiles and configure rulesets must end with the extension <em>.xml</em>
    .</li>
</ul>

<h3><a name="CodingStandards"></a>Coding Standards</h3>

<p> We are using PEAR coding standards. We are using a less strict version of
  these standards, but we do insist that new contributions have phpdoc comments
  and make explicitly declarations about public/protected/private variables and
  methods. If you have suggestions about improvements to Xinc codebase, don't
  hesitate to let us know.</p>

<h2><a name="SystemInitialization"></a>System Initialization</h2>

<p> PHP installations are typically quite customized -- e.g. different memory_limit,
  execution timeout values, etc. The first thing that Xinc does is modify PHP
  INI variables to create a standard PHP environment. This is performed by the
  <em>init layer</em> of Xinc that uses a three-level initialization procedure.
  It basically consists of three different files: </p>

<ul>
  <li>Platform specific wrapper scripts in bin/</li>
  <li>Main application in bin/</li>
  <li>Xinc class in classes/Xinc/</li>
</ul>

<p> At the first look this may seem to be unnecessary overhead. Why three levels
  of initialization? The main reason why there are several entry points is that
  Xinc is build so that other frontends (e.g. PHP-GTK) could be used in place
  of the command line.</p>

<h3><a name="WrapperScripts"></a>Wrapper Scripts</h3>

<p> This scripts are technical not required but provided for the ease of use.
  Imagine you have to type every time you want to build your project: </p>

<pre>php -qC /path/to/Xinc/bin/Xinc.php -verbose all distro snapshot</pre>

<p>
Indeed that is not very elegant. Furthermore if you are lax in
setting your environment variables these script can guess the
proper variables for you. However you should always set them.
</p>

<p> The scripts are platform dependent, so you will find shell scripts for <em>Unix</em>
  like platforms (sh) as well as the batch scripts for <em>Windows</em> platforms.
  If you set-up your path properly you can call Xinc everywhere in your system
  with this command-line (referring to the above example): </p>

<pre>Xinc -v2 all distro</pre>

<h3><a name="MainApplication"></a>The Main Application (Xinc.php)</h3>

<p> This is basically a wrapper for the Xinc class that actually does all the
  logic for you. If you look at the sourcecode for Xinc.php you will see that
  all real initialization is handled in the Xinc class. Xinc.php is simply the
  commandline entry point for Xinc.</p>

<h3><a name="InitClass"></a>The Xinc Class</h3>

<p> Given that all the prior initialization steps passed successfully the Xinc
  is included and <em>Xinc::startup()</em> is invoked by the main application
  script. It sets-up the system components, system constants ini-settings, PEAR
  and some other stuff. The detailed start-up process is as follows: </p>

<ul>
  <li>Start Timer</li>
  <li>Set System Constants</li>
  <li>Set Ini-Settings</li>
  <li>Set Include Paths</li>
</ul>

<p> After the main application completed all operations (successfully or unsuccessfully)
  it calls <em>Xinc::shutdown(EXIT_CODE)</em> that takes care of a proper destruction
  of all objects and a gracefully termination of the program by returning an <em>exit
  code</em> for shell usage (see [See Program Exit Codes] for a list of exit codes).
</p>

<h2><a name="SystemServices"></a>System Services</h2>

<h3><a name="ExceptionSystem"></a>The Exception system</h3>
<p>Xinc uses the PHP5 try/catch/throw Exception system. Xinc defines a number
  of Exception subclasses for more fine-grained handling of Exceptions. Low level
  Exceptions that cannot be handled will be wrapped in a BuildException and caught
  by the outer-most catch() {} block.</p>

<h2><a name="BuildLifecycle"></a>Build Lifecycle</h2>
<p>This section exists to explain -- or try -- how Xinc &quot;works&quot;. Particularly,
  how Xinc procedes through a build file and invokes tasks and types based on
  the tags that it encounters.</p>
<h3><a name=""></a>How Xinc Parses Buildfiles</h3>
<p>Xinc uses an ExpatParser class and PHP's native expat XML functions to handle
  the parsing of build files. The handler classes all extend the Xinc.parser.AbstractHandler
  class. These handler classes &quot;handle&quot; the tags that are found in the
  buildfile.</p>
<p>Core tasks and datatypes are mapped to XML tag names in the defaults.properties
  files -- specifically Xinc/tasks/defaults.properties and Xinc/types/defaults.properties.</p>
<p>It works roughly like this:</p>
<ol>
  <li>Xinc.parser.RootHandler is registered to handle the buildfile XML document</li>
  <li>RootHanlder expects to find exactly one element: &lt;project&gt;. RootHandler
    invokes the ProjectHandler with the attributes from the &lt;project&gt; tag
    or throws an exception if no &lt;project&gt; is found, or if something else
    is found instead.</li>
  <li>ProjectHandler expects to find &lt;target&gt; tags; for these ProjectHandler
    invokes the TargetHandler. ProjectHandler also has exceptions for handling
    certain tasks that can be performed at the top-level: &lt;resolve&gt;, &lt;taskdef&gt;,
    &lt;typedef&gt;, and &lt;property&gt;; for these ProjectHandler invokes the
    TaskHandler class. If a tag is presented that doesn't match any expected tags,
    then ProjectHandler assumes it is a datatype and invokes the DataTypeHandler.</li>
  <li>TargetHandler expects all tags to be either tasks or datatypes and invokes
    the appropriate handler (based on the mappings provided in the defaults.properties
    files).</li>
  <li>Tasks and datatypes can have nested elements, but only if they correspond
    to a create*() method in the task or datatype class. E.g. a nested &lt;param&gt;
    tag must correspond to a createParam() method of the task or datatype.</li>
</ol>
<p><em>... More to come ...</em></p>
<h2><a name="WritingTasks"></a>Writing Tasks</h2>

<h3><a name="CreatingATask"></a>Creating A Task</h3>

<p> We will start creating a rather simple task which basically does nothing more
  than echo a message to the screen. See [below] for the source code and the following
  [below] for the XML definition that is used for this task. </p>

<a href="codelistings.MyEchoTask"></a>
<pre title="Source code of a simple task that echos a message to the screen">
&lt;?php

require_once &quot;Xinc/Task.php&quot;;

class MyEchoTask extends Task {

    /**
     * The message passed in the buildfile.
     */
    private $message = null;

    /**
     * The setter for the attribute &quot;message&quot;
     */
    public function setMessage($str) {
        $this->message = $str;
    }

    /**
     * The init method: Do init steps.
     */
    public function init() {
      // nothing to do here
    }

    /**
     * The main entry point method.
     */
    public function main() {
      print($this->message);
    }
}

?&gt;
</pre>

<p> This code contains a rather simple, but complete Xinc task. It is assumed
  that the file is named <em>MyEchoTask.php</em> and placed in <em>classes/Xinc/tasks/my</em>
  directory. We'll explain the source code in detail shortly. But first we'd like
  to discuss how we should register the task to Xinc so that it can be executed
  during the build process. </p>

<h3><a name="UsingTheTask"></a>Using the Task</h3>

<p> The task shown [above] must somehow get called by Xinc. Therefore it must
  be made available to Xinc so that the buildfile parser is aware a correlating
  XML element and it's parameters. Have a look at the minimalistic buildfile example
  given in [the buildfile below] that does exactly this. </p>

<a name="codelistings.BuildFileEchoTask"></a>
<pre title="A simple buildfile that tests the echo task">
&lt;?xml version=&quot;1.0&quot; ?&gt;

&lt;project name=&quot;test&quot; basedir=&quot;.&quot; default=&quot;myecho&quot;&gt;
    &lt;taskdef name=&quot;myecho&quot; classname=&quot;Xinc.tasks.my.MyEcho&quot; /&gt;

    &lt;target name=&quot;test.myecho&quot;&gt;
      &lt;myecho message=&quot;Hello World&quot; /&gt;
    &lt;/target&gt;
&lt;/project&gt;
</pre>

<p> Besides the XML document prolog and the shell elements that are required to
  properly execute the task (project, target) you'll find the <em>&lt;taskdef&gt;</em>
  element (line 4) that properly registers your custom task to Xinc. For a detailed
  synopsis of the taskdef element see the [description of this task]. </p>

<p>
Now, as we have registered the task by assigning a name and the
worker class ([see source code above]) it is ready for usage within
the <em>&lt;target&gt;</em> context (line 8). You see that we pass
the message that our task should echo to the screen via an XML
attribute called "message".
</p>

<h3><a name="SourceDiscussion"></a>Source Discussion</h3>

<p>
No that you've got the knowledge to execute the task in a
buildfile it's time to discuss how everything works.
</p>

<h3><a name="TaskStructure"></a>Task Structure</h3>

<p>
All files containing the definition of a task class follow a
common well formed structure:
</p>

<ul>
  <li>Package imports to import all required packages</li>
  <li>The class declaration and definition</li>
  <li>The class's properties</li>
  <li>The class's constructor</li>
  <li>Setter methods for each XML attribute</li>
  <li>The <em>init()</em> method</li>
  <li>The <em>main()</em> method</li>
  <li>Arbitrary <em>private</em> (or <em>protected</em>)<em> </em>class methods</li>
</ul>

<h3><a name="PackageImports"></a>Package Imports</h3>

<p> Always import all the packages/files needed for this task in full written
  notation. Furthermore you should always import <em>Xinc.Task</em> at the very
  top of your <em>import</em> block. Then import all other required system or
  proprietary packages. Import works quite similar to PHP's native <em>include_once</em>
  but with some Java-stylish additions providing a file system independent notation.
</p>

<p>
For a more in-depth explanation of the used package mechanism
and the package support API reference, see [package support]
For a list of stock packages provided with Xinc, see
[package list].
</p>

<h3><a name="ClassDeclaration"></a>Class Declaration</h3>

<p>
If you look at line 5 in [the source code of the task] you will find
the <em>class declaration</em>. This will be familiar to you if
you are experienced with OOP in PHP (we assume here that you are).
Furthermore there are some fine-grained rules you must obey when
creating the classes (see also,[naming and coding standards]):
</p>

<ul>
  <li>Your classname must be exactly like the taskname you are going to implement
    plus the suffix "Task". In our example case the classname is <em>MyEchoTask</em>
    (constructed by the taskname "myecho" plus the suffix "task"). The upper/lower
    case casing is currently only for better reading. However, it is encouraged
    that you use it this way.</li>
  <li>The task class you are creating must at least extend "Task" to
      inherit all task specific methods.</li>
</ul>

<h3><a name="ClassProperties"></a>Class Properties</h3>

<p>
The next lines you are coding are class properties. Most of them
are inherited from the Task superclass, so there's not need to
redeclare them. Nevertheless you should <em>declare</em> the
following ones by your own:
</p>

<ul>
  <li>Taskname. Always hard code the <em>taskname</em> property that
      equals the name of the XML element that your task claims. Currently
      this information is not used - but it will be in the future.</li>
  <li>Your arbitrary properties that reflect the XML
      attributes/elements which your task accepts.</li>
</ul>

<p>
In the MyEchoTask example the coded properties can be found in
lines 7 to 11. Give you properties meaningful descriptive names
that clearly state their function within the context. A couple of
properties are inherited from the superclass that must not be
declared in the properties part of the code.
</p>

<p>
For a list of inherited properties (most of them are reserved,
so be sure not to overwrite them with your own) can be found in the
"Xinc API Reference" in the docs/api/ directory.
</p>

<h3><a name="TheConstructor"></a>The Constructor</h3>

<p> The next block that follows is the class's constructor. It must be present
  and call at least the constructor or the parent class. Of course, you can add
  some initialization data here. It is recommended that you <em>define</em> your
  prior declared properties here.</p>

<h3><a name="SetterMethods"></a>Setter Methods</h3>

<p>
As you can see in the XML definition of our task ([see buildfile above]
, line 9) there is an attribute defined with the
task itself, namely "message" with a value of the the text string
that our task should echo. The task must somehow become aware of
the attribute name and the value. Therefore the <em>setter
methods</em> exist.
</p>

<p>For each attribute you want to import to the task's namespace
you have to define a method named exactly after the very attribute
plus the string "Set" prepended. This method accepts exactly one
parameter that holds the value of the attribute. No you can set the
value an class internal property to the value incoming via the
setter method.</p>

<p>In out example the setter is named <em>SetMessage</em> , because the XML attribute
  the echo task accepts is "message". SetMessage now takes the string "Hello World"
  provided by the parser and sets the value of the internal class property <em>$strMessage</em>
  to "Hello World". It is now available to the task for further disposal.</p>

<h3><a name="CreatorMethods"></a>Creator Methods</h3>

<p> Creator methods allow you to manage nested XML tags in your new Xinc Task.</p>

<h3><a name="InitMethod"></a><em>init()</em> Method</h3>

<p> The init method gets called when the &lt;taskname&gt; xml element closes.
  It must be implemented even if it does nothing like in the example above. You
  can do init steps here required to setup your task object properly. After calling
  the Init-Method the task object remains untouched by the parser. Init should
  not perform operations related somehow to the action the task performs. An example
  of using init may be cleaning up the $strMessage variable in our example (i.e.
  trim($strMessage)) or importing additional workers needed for this task. </p>

<p>
The init method should return true or an error object evaluated
by the governing logic. If you don't implement init method, Xinc
will shout down with a fatal error.
</p>

<h3><a name="MainMethod"></a><em>main()</em> Method</h3>

<p> There is exactly one entry entry point to execute the task. It is called after
  the complete buildfile has been parsed and all targets and tasks have been scheduled
  for execution. From this point forward the very implementation of the tasks
  action starts. In case of our example a message (imported by the proper setter
  method) is Logged to the screen through the system's "Logger" service (the very
  action this task is written for). The <em>Log()</em> method-call in this case
  accepts two parameters: a event constant and the message to log. </p>

<p>
For a in-depth list of system constants see <a
href="../document/appendix/AppB.htm#95990">See System
Constants</a>. For the detailed reference on the system's logger
see [REF] and the Xinc API docs located in the docs/
subdirectory.
</p>

<h3><a name="ArbitraryMethods"></a>Arbitrary Methods</h3>

<p> For the more or less simple cases (as our example) all the logic of the task
  is coded in the Main() method. However for more complex tasks common sense dictates
  that particular action should be swapped to smaller, logically contained units
  of code. The most common way to do this is separating logic into private class
  methods - and in even more complex tasks in separate libraries. </p>

<pre>private function myPrivateMethod() {
    // definition
}
</pre>

<p>
More reading on this particular topic can be sound in See Naming
And Coding Standards.
</p>

<h3><a name="Summary"></a>Summary</h3>

<p>
You now have learned how to create and use a task. However we
guess there are much questions open concerning task development:
"How do I use filesets and mapper" or "How do I implement custom
nested tags in my task". Most of these concepts and the proper
usage will be clear if you continue reading this doc. Additionally
you might check out the appendices for the advanced examples (See
Advanced Task Example).
</p>

<h2><a name="WritingTypes"></a>Writing Types</h2>

<p>You should only create a standalone Type if the Type needs to be shared by
  more than one Task. If the Type is only needed for a specific Task -- for example
  to handle a special parameter or other tag needed for that Task -- then the
  Type class should just be defined within the same file as the Task. (For example,
  <em>Xinc/filters/XSLTFilter.php</em> also includes an XSLTParam class that
  is not used anywhere else.)</p>
<p>For cases where you do need a more generic Type defined, you can create your
  own Type class -- similar to the way a Task is created [<a href="#WritingTasks">Writing
  Tasks </a>].</p>
<h3>Creating a DataType</h3>
<p>Type classes need to extend the abstract DataType class. Besides providing
  a means of categorizing types, the DataType class provides the methods necessary
  to support the &quot;refid&quot; attribute. (All types can be given an id, and
  can be referred to later using that id.)</p>
<p>In this example we are creating a DSN type because we have written a number
  of DB-related Tasks, each of which need to know how to connect to the database;
  instead of having database parameters for each task, we've created a DSN type
  so that we can identify the connection params once and then use it in all our
  db Tasks.</p>
<pre title="example Type">require_once &quot;Xinc/types/DataType.php&quot;;

/**
 * This Type represents a DB Connection.
 */
class DSN extends DataType {

  private $url;
  private $username;
  private $password;
  private $persistent = false;

  /**
   * Sets the URL part: mysql://localhost/mydatabase
   */
  public function setUrl($url) {
    $this-&gt;url = $url;
  }

  /**
   * Sets username to use in connection.
   */
  public function setUsername($username) {
    $this-&gt;username = $username;
  }

  /**
   * Sets password to use in connection.
   */
  public function setPassword($password) {
    $this-&gt;password = $password;
  }

  /**
   * Set whether to use persistent connection.
   * @param boolean $persist
   */
  public function setPersistent($persist) {
    $this-&gt;persistent = (boolean) $persist;
  }

  public function getUrl(Project $p) {
    if ($this-&gt;isReference()) {
      return $this-&gt;getRef($p)-&gt;getUrl($p);
    }
    return $this-&gt;url;
  }

  public function getUsername(Project $p) {
    if ($this-&gt;isReference()) {
      return $this-&gt;getRef($p)-&gt;getUsername($p);
    }
    return $this-&gt;username;
  }

  public function getPassword(Project $p) {
    if ($this-&gt;isReference()) {
      return $this-&gt;getRef($p)-&gt;getPassword($p);
    }
    return $this-&gt;password;
  }

  public function getPersistent(Project $p) {
    if ($this-&gt;isReference()) {
      return $this-&gt;getRef($p)-&gt;getPersistent($p);
    }
    return $this-&gt;persistent;
  }

  /**
   * Gets a combined hash/array for DSN as used by PEAR.
   * @return array
   */
  public function getPEARDSN(Project $p) {
    if ($this-&gt;isReference()) {
      return $this-&gt;getRef($p)-&gt;getPEARDSN($p);
    }

    include_once 'DB.php';
    $dsninfo = DB::parseDSN($this-&gt;url);
    $dsninfo['username'] = $this-&gt;username;
    $dsninfo['password'] = $this-&gt;password;
    $dsninfo['persistent'] = $this-&gt;persistent;

    return $dsninfo;
  }

  /**
   * Your datatype must implement this function, which ensures that there
   * are no circular references and that the reference is of the correct
   * type (DSN in this example).
   *
   * @return DSN
   */
  public function getRef(Project $p) {
    if ( !$this-&gt;checked ) {
      $stk = array();
      array_push($stk, $this);
      $this-&gt;dieOnCircularReference($stk, $p);
    }
    $o = $this-&gt;ref-&gt;getReferencedObject($p);
    if ( !($o instanceof DSN) ) {
      throw new BuildException($this-&gt;ref-&gt;getRefId().&quot; doesn't denote a DSN&quot;);
    } else {
      return $o;
    }
  }

}</pre>
<h3><a name="UsingType"></a>Using the DataType</h3>
<p>The <em>TypedefTask</em> provides a way to &quot;declare&quot; your type so
  that you can use it in your build file. Here is how you would use this type
  in order to define a single DSN and use it for multiple tasks. (Of course you
  could specify the DSN connection params each time, but the premise behind needing
  a DSN datatype was to avoid specifying the connection parameters for each task.)</p>
<a name="codelistings.DSN"></a>
<pre title="A simple buildfile that tests the echo task">
&lt;?xml version=&quot;1.0&quot; ?&gt;

&lt;project name=&quot;test&quot; basedir=&quot;.&quot;&gt;

  &lt;typedef name=&quot;dsn&quot; classname=&quot;myapp.types.DSN&quot; /&gt;

  &lt;dsn
      id=&quot;maindsn&quot;
      url=&quot;mysql://localhost/mydatabase&quot;
      username=&quot;root&quot;
      password=&quot;&quot;
      persistent=&quot;false&quot; /&gt;

  &lt;target name=&quot;main&quot;&gt;

    &lt;my-special-db-task&gt;
	     &lt;dsn refid=&quot;maindsn&quot;/&gt;
    &lt;/my-special-db-task&gt;

    &lt;my-other-db-task&gt;
      &lt;dsn refid=&quot;maindsn&quot;/&gt;
    &lt;/my-other-db-task&gt;

  &lt;/target&gt;

&lt;/project&gt;
</pre>

<h3><a name="TypeSourceDisc"></a>Source Discussion</h3>
<h4>Getters &amp; Setters</h4>
<p>You must provide a setter method for every attribute you want to set from the
  XML build file. It is good practice to also provide a getter method, but in
  practice you can decide how your tasks will use your task. In the example above,
  we've provided a getter method for each attribute and we've also provided an
  additional method: DSN::getPEARDSN() which returns the DSN hash array used by
  PEAR::DB, PEAR::MDB, and Creole. Depending on the needs of the Tasks using this
  DataType, we may only wish to provide the getPEARDSN() method rather than a
  getter for each attribute.</p>
<p> Also important to note is that the getter method needs to check to see whether
  the current DataType is a reference to a previously defined DataType -- the
  DataType::isReference() exists for this purpose. For this reason, the getter
  methods need to be called with the current project, because References are stored
  relative to a project.</p>
<h4>The getRef() Method</h4>
<p>The getRef() task needs to be implemented in your Type. This method is responsible
  for returning a referenced object; it needs to check to make sure the referenced
  object is of the correct type (i.e. you can't try to refer to a RegularExpresson
  from a DSN DataType) and that the reference is not circular. </p>
<p>You can probably just copy this method from an existing Type and make the few
  changes that customize it to your Type.</p>
<h2><a name="WritingMappers"></a>Writing Mappers</h2>
<p>Writing your own filename mapper classes will allow you to control how names
  are transformed in tasks like CopyTask, MoveTask, XSLTTask, etc. In some cases
  you may want to extend existing mappers (e.g. creating a GlobMapper that also
  transforms to uppercase); in other cases, you may simply want to create a very
  specific name transformation that isn't easily accomplished with other mappers
  like GlobMapper or RegexpMapper.</p>
<h3><a name="CreatingMapper"></a>Creating a Mapper</h3>
<p>Writing filename mappers is simplified by interface support in PHP5. Essentially,
  your custom filename mapper must implement <em>Xinc.mappers.FileNameMapper</em>.
  Here's an example of a filename mapper that creates DOS-style file names. For
  this example, the &quot;to&quot; and &quot;from&quot; attributes are not needed
  because all files will be transformed. To see the &quot;to&quot; and &quot;from&quot;
  attributes in action, look at <em>Xinc.mappers.GlobMapper</em> or <em>Xinc.mappers.RegexpMapper</em>.
</p>
<pre title="sample mapper">require_once &quot;Xinc/mappers/FileNameMapper.php&quot;;

/**
 * A mapper that makes those ugly DOS filenames.
 */
class DOSMapper implements FileNameMapper {

  /**
   * The main() method actually performs the mapping.
   *
   * In this case we transform the $sourceFilename into
   * a DOS-compatible name.  E.g.
   * ExtendingXinc.html -> EXTENDI~.DOC
   *
   * @param string $sourceFilename The name to be coverted.
   * @return array The matched filenames.
   */
  public function main($sourceFilename) {

    $info = pathinfo($sourceFilename);
    $ext = $info['extension'];
    // get basename w/o extension
    $bname = preg_replace('/\.\w+\$/', '', $info['basename']);

    if (strlen($bname) &gt; 8) {
      $bname = substr($bname,0,7) . '~';
    }

    if (strlen($ext) > 3) {
      $ext = substr($bname,0,3);
    }

    if (!empty($ext)) {
      $res = $bname . '.' . $ext;
    } else {
      $res = $bname;
    }

    return (array) strtoupper($res);
  }

  /**
   * The &quot;from&quot; attribute is not needed here, but method must exist.
   */
  public function setFrom($from) {}

	 /**
   * The &quot;from&quot; attribute is not needed here, but method must exist.
   */
  public function setTo($to) {}

}
</pre>
<h3><a name="UsingMapper"></a>Using the Mapper</h3>
<p>Assuming that this mapper is saved to <em>myapp/mappers/DOSMapper.php </em>(relative
  to a path on PHP's <em>include_path</em> or in <em>PHP_CLASSPATH</em> env variable),
  then you would refer to it like this in your build file:</p>
<pre title="using example mapper in build file">&lt;mapper classname=&quot;myapp.mappers.DOSMapper&quot;/&gt;</pre>

</body>
</html>
